package com.boarsoft.boar.agent.soagov;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.util.StringUtils;

import com.boarsoft.boar.soagov.health.HttpCheckRunner;
import com.boarsoft.common.Util;
import com.boarsoft.common.util.InetUtil;
import com.boarsoft.soagov.bean.AppInstReg;
import com.boarsoft.soagov.registry.ServiceRegistry;
import com.boarsoft.webapp.HttpServiceHandler;

/**
 * 当前应用启动时会到配置中心查询需要监控的应用实例<br>
 * 如果服务器上的应用部署相对固定，也可以采用从配置中心拉取配置文件的方式。
 * 
 * @author Mac_J
 *
 */
public class SoagovHttpSidecar implements HttpServiceHandler, Runnable {
	private static final Logger log = LoggerFactory.getLogger(SoagovHttpSidecar.class);

	@Autowired
	protected ApplicationContext appCtx;
	@Autowired
	protected ServiceRegistry registry;

	/** 用XML方式注入以下内容 */
	protected String serverAddr;
	protected ScheduledExecutorService scheduler;

	/** 运行时属性 */
	protected Map<String, HttpCheckRunner> runnerMap = new ConcurrentHashMap<>();
	
	protected String ip = InetUtil.getIp();

	@PostConstruct
	public void init() {
		//TODO 暂时关闭
		scheduler.scheduleWithFixedDelay(this, 10L, 10L, TimeUnit.SECONDS);
	}

	@Override
	public void run() {
		if (runnerMap.isEmpty()) {
			log.info("Query app inst list on {}", ip);
			try {
				// 查询当前节点部署了哪些服务需要检测
				List<AppInstReg> lt = registry.list(ip);
				for (AppInstReg o : lt) {
					this.addRunner4(o);
				}
			} catch (Exception e) {
				log.warn("Failed to get app inst list on {}", ip, e);
				return;
			}
		} else {
			for (HttpCheckRunner r : runnerMap.values()) {
				if (r.getStatus()) {
					continue;
				}
				log.warn("Remove health check runner {}", r.getAddr());
				runnerMap.remove(r.getAddr());
				r.stop();
			}
		}
	}

	public HttpCheckRunner addRunner4(AppInstReg r) {
		String addr = r.getAddr();
		log.info("Add app inst {} to health check list", addr);
		if (runnerMap.containsKey(addr)) {
			return runnerMap.get(addr);
		}
		synchronized (runnerMap) {
			if (runnerMap.containsKey(addr)) {
				return runnerMap.get(addr);
			}
			// 此bean是prototype类型，目的是当一台服务器上部署同一应用的多个实例时，端口和部署路径是可变的
			HttpCheckRunner hcr = appCtx.getBean(r.getHandler(), HttpCheckRunner.class);
			hcr.setAddr(addr);
			hcr.setPath(r.getPath());
			hcr.setAppCode(r.getAppCode());
			int interval = hcr.getInterval();
			// 本地只有checker没有handler，两者同名
			hcr.setFuture(scheduler.scheduleWithFixedDelay(//
					hcr, interval, interval, TimeUnit.SECONDS));
			runnerMap.put(r.getAddr(), hcr); // 在本地注册
			return hcr;
		}
	}

	@Override
	public boolean process(HttpServletRequest req, HttpServletResponse rsp) throws ServletException, IOException {
		int port = Integer.parseInt(req.getParameter("port"));
		AppInstReg reg = registry.find(ip, port);
		if (reg == null) {
			log.warn("Can not find app inst {}:{}", ip, port);
			String appId = req.getParameter("appId");
			String handler = req.getParameter("handler");
			if (Util.strIsEmpty(handler) && Util.strIsNotEmpty(appId)) {
				handler = registry.getHandler(appId);
				if (Util.strIsEmpty(handler)) {
					log.warn("Can not get handler config of app {}", appId);
				}
			}
			reg = new AppInstReg();
			reg.setAddr(new StringBuilder().append(ip)//
					.append(":").append(port).toString());
			reg.setPath(req.getParameter("path"));
			reg.setHandler(handler);
			reg.setAppCode(reg.getAppCode());
			return false;
		}
		switch (req.getRequestURI()) {
		case "/soagov/app/inst/register.do":
			this.addRunner4(reg);
			break;
		case "/soagov/app/inst/deregister.do":
			runnerMap.remove(reg.getAddr()); // 在本地注销
			break;
		}
		// 替换原请求中的域名为实际提供服务的应用地址
		StringBuffer sb = req.getRequestURL();
		// http://xxx/.... 替换xxx部份
		int i = sb.substring(7).indexOf("/") + 7;
		sb.replace(7, i, serverAddr);
		String qs = req.getQueryString();
		if (!StringUtils.isEmpty(qs)) {
			sb.append("?").append(qs);
		}
		String url = sb.toString();
		log.debug("rewrite {} to {}", req.getRequestURI(), url);
		req.setAttribute("rewriteUrl", url);
		return false;
	}

	@PreDestroy
	public void destroy() {
		for (HttpCheckRunner runner : runnerMap.values()) {
			try {
				runner.getFuture().cancel(true);
			} catch (Exception e) {
				log.warn("Error on cancel health check runner, {}", e.getMessage());
			}
		}
	}

	public Map<String, HttpCheckRunner> getRunnerMap() {
		return runnerMap;
	}

	public void setRunnerMap(Map<String, HttpCheckRunner> runnerMap) {
		this.runnerMap = runnerMap;
	}

	public ScheduledExecutorService getScheduler() {
		return scheduler;
	}

	public void setScheduler(ScheduledExecutorService scheduler) {
		this.scheduler = scheduler;
	}

	public String getIp() {
		return ip;
	}

	public void setIp(String ip) {
		this.ip = ip;
	}

	public String getServerAddr() {
		return serverAddr;
	}

	public void setServerAddr(String serverAddr) {
		this.serverAddr = serverAddr;
	}
}